/**
 * @作者 faner
 * @创建时间 2020/11/17 18:33
 */
package com.faner.fast.advice;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.faner.fast.config.CodeMessageConfiguration;
import com.faner.fast.constant.enums.CodeEnum;
import com.faner.fast.exception.HttpException;
import com.faner.fast.util.R;
import com.faner.fast.util.RequestUtil;
import com.faner.fast.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestControllerAdvice
@Slf4j
public class GlobalExceptionAdvice {

    @Value("${spring.servlet.multipart.max-file-size:20M}")
    private String maxFileSize;

    /**
     * 捕获未知异常
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)
    public R processException(HttpServletRequest request, Exception e) {
        log.error("[processException]全局异常Exception拦截 ex: [{}]", e);
        return R.builder()
                .code(CodeEnum.INTERNAL_SERVER_ERROR.getCode())
                .msg(CodeEnum.INTERNAL_SERVER_ERROR.getMessage())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 无权限
     */
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(code = HttpStatus.FORBIDDEN)
    public R handleAccessRE(HttpServletRequest request,AccessDeniedException e){
        log.error("[processException]无权限异常 AccessDeniedException拦截 ex: [{}]", e);
        return R.builder()
                .code(CodeEnum.FAIL_ACL_DENY.getCode())
                .msg(CodeEnum.FAIL_ACL_DENY.getMessage())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * HttpException
     */
    @ExceptionHandler({HttpException.class})
    public R processException(HttpException exception, HttpServletRequest request, HttpServletResponse response) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        R r = new R();
        r.setPath(RequestUtil.getSimpleRequest(request));
        int code = exception.getCode();
        r.setCode(code);
        response.setStatus(exception.getHttpCode());
        String errorMessage = CodeMessageConfiguration.getMessage(code);
        if (StrUtil.isBlank(errorMessage) ) {
            r.setMsg(exception.getMessage());
            log.error("", exception);
        } else {
//            r.setMsg(errorMessage);
            r.setMsg(exception.getMessage());
            log.error("", exception.getClass().getConstructor(int.class, String.class).newInstance(CodeEnum.INTERNAL_SERVER_ERROR.getCode(), errorMessage));
        }
        return r;
    }

//    /**
//     * 捕获自定义异常
//     *
//     * @param req
//     * @param e
//     * @return
//     */
//    @ExceptionHandler(HttpException.class)
//    public ResponseEntity<R> handleHttpException(HttpServletRequest req, HttpException e) {
//        log.error("[handleHttpException] 全局拦截自定义异常: [{}]", e);
//        String requestUrl = req.getRequestURI();
//        String method = req.getMethod();
//
//      R.builder()
//                .path(method + " " + requestUrl)
//                .code(e.);
//        if(null == exceptionCode || null == exceptionCode.getMessage(e.getCode())){
//            log.warn("ex : {} ", exceptionCode);
//            log.warn("请配置自己的业务错误码....");
//            log.warn("当前异常 {}",e.getMessage());
//            builder.message(e.getMessage());
//        }else{
//            builder.message(exceptionCode.getMessage(e.getCode()));
//        }
//
//        UnifyResponseVO unifyResponseVO = builder.build();
//
//        HttpHeaders headers = new HttpHeaders();
//        headers.setContentType(MediaType.APPLICATION_JSON);
//        HttpStatus httpStatus = HttpStatus.resolve(e.getHttpStatusCode());
//        ResponseEntity<UnifyResponseVO> res = new ResponseEntity<>(unifyResponseVO, headers, httpStatus);
//        return res;
//    }

    /**
     * 直接拦截缺少参数的异常
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(HttpServletRequest request, MissingServletRequestParameterException e) {
        log.error("Missing Request Parameter[缺少参数的异常]: [{}]", e.getMessage(), e);
        String message = String.format("Missing Request Parameter[缺少参数是]:[ %s ]", e.getParameterName());
        return R.builder()
                .code(CodeEnum.PARAMETER_ERROR.getCode())
                .msg(message)
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 参数类型定义是与前端传入类型不匹配
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(HttpServletRequest request, MethodArgumentTypeMismatchException e) {
        log.error("Method Argument Type Mismatch[参数类型不匹配] :[{}]", e);
        String message = String.format("Method Argument Type Mismatch[参数类型不匹配]: %s", e.getName());
        return R.builder()
                .code(CodeEnum.PARAMETER_ERROR.getCode())
                .msg(message)
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 统一处理请求参数校验(实体对象传参)
     *
     * @param e BindException
     * @return R
     */
    @ExceptionHandler(BindException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(HttpServletRequest request, BindException e) {
        log.error("Bind Exception :[{}]", e);
        FieldError error = e.getFieldError();
        String message = String.format("%s:%s", error.getField(), error.getDefaultMessage());
        return R.builder()
                .code(CodeEnum.PARAMETER_ERROR.getCode())
                .msg(message)
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 使用@Valid 验证WordsRequest 入参合法性
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(HttpServletRequest request, MethodArgumentNotValidException e) {
        log.error("Method Argument Not Valid :[{}]", e);
        BindingResult result = e.getBindingResult();
        List<ObjectError> errors = result.getAllErrors();
        Map<String, Object> msg = new HashMap<>();
        errors.forEach(error -> {
            if (error instanceof FieldError) {
                FieldError fieldError = (FieldError) error;
                msg.put(StrUtil.toUnderlineCase(fieldError.getField()), fieldError.getDefaultMessage());
            } else {
                msg.put(StrUtil.toUnderlineCase(error.getObjectName()), error.getDefaultMessage());
            }
        });
        StringBuilder message = new StringBuilder() ;
        if(!"dev".equals(SpringContextHolder.getActiveProfile())) {
            for (Map.Entry<String, Object> entry : msg.entrySet()) {
                message.append(entry.getValue().toString()).append(";");
            }
        }else{
            message = message.append(JSONUtil.toJsonStr(msg));
        }
        return R.builder()
                .code(CodeEnum.PARAMETER_ERROR.getCode())
                .msg(message.toString())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 进行服务间的rpc调用没有对参数进行json序列化 requestBody为空
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(HttpServletRequest request, HttpMessageNotReadableException e) {
        log.error("Message Not Readable", e);
        R r = new R<>();
        String errorMessage = CodeMessageConfiguration.getMessage(10170);
        if (StrUtil.isBlank(errorMessage)) {
            r.setMsg(e.getMessage());
        } else {
            r.setMsg(errorMessage);
        }
        r.setCode(CodeEnum.PARAMETER_ERROR.getCode());
        r.setPath(RequestUtil.getSimpleRequest(request));
        return r;
    }

    /**
     * 统一处理请求参数校验(普通传参)
     *
     * @param e ConstraintViolationException
     * @return FebsResponse
     */
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(HttpServletRequest request, ConstraintViolationException e) {
        log.error("ConstraintViolationException exception", e);
        StringBuilder message = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
            message.append(constraintViolation.getMessage()).append("|");
        }
        return R.builder()
                .code(CodeEnum.PARAMETER_ERROR.getCode())
                .msg(message.toString())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 路径不被匹配
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(code = HttpStatus.NOT_FOUND)
    public R processException(HttpServletRequest request, NoHandlerFoundException e) {
        log.error("404 Not Found", e);
        return R.builder()
                .code(CodeEnum.NOT_FOUND.getCode())
                .msg(e.getMessage())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 请求方式错误
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseStatus(code = HttpStatus.METHOD_NOT_ALLOWED)
    public R processException(HttpServletRequest request, HttpRequestMethodNotSupportedException e) {
        log.error("Request Method Not Supported", e);
        return R.builder()
                .code(CodeEnum.METHOD_NOT_ALLOWED.getCode())
                .msg(e.getMessage())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * 文件类型错误
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    @ResponseStatus(code = HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    public R processException(HttpServletRequest request, HttpMediaTypeNotSupportedException e) {
        log.error("Media Type Not Supported", e);
        return R.builder()
                .code(CodeEnum.FILE_EXTENSION.getCode())
                .msg(e.getMessage())
                .path(RequestUtil.getSimpleRequest(request))
                .build();
    }

    /**
     * TypeMismatchException BeanUtil.copy 赋值 名称相同类型不同 赋值错误
     */
    @ExceptionHandler({TypeMismatchException.class})
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(TypeMismatchException exception, HttpServletRequest request, HttpServletResponse response) {
        log.error("", exception);
        R r = new R();
        r.setPath(RequestUtil.getSimpleRequest(request));
        r.setMsg(exception.getMessage());
        r.setCode(CodeEnum.PARAMETER_ERROR.getCode());
        return r;
    }

    /**
     * MaxUploadSizeExceededException 超过最大上传文件大小
     */
    @ExceptionHandler({MaxUploadSizeExceededException.class})
    @ResponseStatus(code = HttpStatus.PAYLOAD_TOO_LARGE)
    public R processException(MaxUploadSizeExceededException exception, HttpServletRequest request) {
        log.error("超过最大上传文件大小-", exception);
        String errorMessage = CodeMessageConfiguration.getMessage(10180);
        R r = new R<>();
        if (StrUtil.isBlank(errorMessage)) {
            r.setMsg(exception.getMessage());
        } else {
            r.setMsg(errorMessage + maxFileSize);
        }
        r.setCode(CodeEnum.FILE_TOO_LARGE.getCode());
        return r;
    }

    /**
     * ServletException
     */
    @ExceptionHandler({ServletException.class})
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public R processException(ServletException exception, HttpServletRequest request) {
        log.error("", exception);
        R r = new R<>();
        r.setPath(RequestUtil.getSimpleRequest(request));
        r.setMsg(exception.getMessage());
        r.setCode(CodeEnum.FAIL.getCode());
        return r;
    }
}
